#include "st7567_spi.h"

#if USE_LCD_PANEL_ST7567_SPI_4WIRE_1LANE_1IF
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "lvgl/lv_core/lv_vdb.h"
#include "lvgl/lv_core/lv_refr.h"
#include "lvgl/lv_hal/lv_hal_tick.h"
#include "crane_lcdc.h"
#include "crane_lcd_spi.h"
#include "../common/utils.h"
#include "../common/lv_pm.h"
#include "../../board.h"
#include "lv_conf.h"

#define USE_LCD_CONTROLLER   1

const static struct spi_timing lcd_st7567_spi_timing = {
    3250, /* KHz */
    2500, /* KHz*/ /*not support read, no use*/
};

const static struct spi_info lcd_st7567_spi_4wire_1lane_1if_info = {
    4,
    1,
    1,
    SPI_FORMAT_RGB565,
    0,
    SPI_EDGE_RISING,
    SPI_ENDIAN_MSB,
    0,
    &lcd_st7567_spi_timing,
};

const uint8_t pagemap[] = { 7, 6, 5, 4, 3, 2, 1, 0 };

const unsigned st7567_id = 0x7567;

static lcd_context g_lcd_ctx;
static uint8_t lcd_fb[LV_HOR_RES * LV_VER_RES / 8] = {0xAA, 0xAA};

static int st7567_panel_init(unsigned width, unsigned height)
{
    int i = 0, j = 0;

    lcd_spi_write_cmd(0xE2,8);   //set softreset
    lcd_spi_write_cmd(0xF8,8);   //set booster
    lcd_spi_write_cmd(0x00,8);   //5
    lcd_spi_write_cmd(0x25,8);   //set resistor ratio
    lcd_spi_write_cmd(0x2F,8);   //select power circuit
    lcd_spi_write_cmd(0xA2,8);   //set bias: 1/9 bias
    lcd_spi_write_cmd(0x81,8);   //set EV
    lcd_spi_write_cmd(0x1B,8);   //contrast value 0x1B(27)
    //lcd_spi_write_cmd(0xA1,8);
    lcd_spi_write_cmd(0xA0,8);   //set SEG direction
    //lcd_spi_write_cmd(0xC0,8);
    lcd_spi_write_cmd(0xC8,8);  //set COM direction
    lcd_spi_write_cmd(0x40,8);   //start line adress
    //lcd_spi_write_cmd(0xA7,8);
    lcd_spi_write_cmd(0xA6,8);   //inverse
    lcd_spi_write_cmd(0xA4,8);
    lcd_spi_write_cmd(0xB0,8);   //page address

    for(i = 0; i < height; i = i+8){
        lcd_spi_write_cmd(0x10, 8);
        lcd_spi_write_cmd(0x00, 8);
        lcd_spi_write_cmd(0xB0+i/8, 8);

        for(j = 0; j < width; j++){
            lcd_spi_write_data(0xaa, 8);
        }
    }

    lcd_spi_write_cmd(0xaf, 8); // Display ON
    return 0;
}

static void st7567_panel_invalid(uint32_t start_x,
                                  uint32_t start_y,  uint32_t end_x, uint32_t end_y)
{
    lcd_spi_write_cmd(0xB0 + pagemap[start_y / 8], 8);

    lcd_spi_write_cmd(0x10, 8);
    lcd_spi_write_cmd(0x0, 8);
}

static int st7567_spi_read_id(unsigned *id)
{
    *id = st7567_id;
    return 0;
}

static void st7567_spi_onoff(int on)
{
    if (on) {
        lcd_spi_write_cmd(0xa4, 8); // Display ON
        lcd_spi_write_cmd(0xaf, 8); // Display ON
    }
    else {   /* off */
        lcd_spi_write_cmd(0xae, 8); // Display OFF
        lcd_spi_write_cmd(0xa5, 8); // Display OFF
    }
}

static int panel_detect(void)
{
    printf("LCD panel st7567: not support detected yet!!\n");

    return 0;
}

static void st7567_suspend(void *arg)
{
    struct crane_panel_t *p = arg;
    printf("st7567_suspend\n");
    p->panel_onoff(0);
}

static void st7567_resume(void *arg)
{
    struct crane_panel_t *p = arg;
    printf("st7567_resume\n");
    p->panel_onoff(1);
    lv_obj_invalidate(lv_scr_act());
    lv_refr_now();
}

static int st7567_spi_probe(unsigned lcd_clk)
{
    lv_pm_info_t pm_info;

    lcd_spi_init(lcd_clk, &lcd_st7567_spi_4wire_1lane_1if_info);

    lcdc_reset_panel();
    st7567_panel_init(LV_HOR_RES, LV_VER_RES);

    if (panel_detect() < 0) {
        return -1;
    }

    memset(&g_lcd_ctx, 0, sizeof(lcd_context));
    g_lcd_ctx.output_config.data_lane_num = lcd_st7567_spi_4wire_1lane_1if_info.data_lane_num;
    g_lcd_ctx.bg_color = 0xffffff;
    g_lcd_ctx.alpha_mode = LCD_ALPHA_MODE_NORMAL;
    g_lcd_ctx.output_config.format = lcd_st7567_spi_4wire_1lane_1if_info.format;

    pm_info.suspend = st7567_suspend;
    pm_info.resume = st7567_resume;
    pm_info.data = &st7567_spi;
    pm_info.part = LV_PM_PART_DISPLAY;
    lv_pm_register(&pm_info);

    memset(lcd_fb, 0xaa, LV_HOR_RES*LV_VER_RES/8);
    return 0;
}

void st7567_get_fb(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t *color_p)
{
    /*Truncate the area to the screen*/
    int32_t act_x1 = x1 < 0 ? 0 : x1;
    int32_t act_y1 = y1 < 0 ? 0 : y1;
    int32_t act_x2 = x2 > LV_HOR_RES - 1 ? LV_HOR_RES - 1 : x2;
    int32_t act_y2 = y2 > LV_VER_RES - 1 ? LV_VER_RES - 1 : y2;

    int32_t x, y;
    int32_t i;

    /*Refresh frame buffer*/
    for(y = act_y1; y <= act_y2; y++) {
        for(x = act_x1; x <= act_x2; x++) {
            i = x + (y / 8)*LV_HOR_RES;
#if USE_LCD_CONTROLLER
            i += ((x%2 == 0)?1:-1);
#endif
            if(lv_color_to1(*color_p) != 0) {
                lcd_fb[i] &= ~(1 << (7 - (y % 8)));
            } else {
                lcd_fb[i] |= (1 << (7 - (y % 8)));
            }
            color_p ++;
        }
        color_p += x2 - act_x2; /*Next row*/
    }
}
static void st7567_spi_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t *color_p)
{
    st7567_get_fb(x1, y1, x2, y2, color_p);
#if USE_LCD_CONTROLLER
    int i = 0;
    //uint32_t start = lv_tick_get();
    unsigned long buf_len = (LV_HOR_RES * LV_VER_RES) /8;
    flush_cache((unsigned long)lcd_fb, buf_len);
    g_lcd_ctx.layer_config_osd1.format = LCD_FORMAT_RGB565;
    g_lcd_ctx.layer_config_osd1.layer_enable = 1;
    g_lcd_ctx.layer_config_osd1.stride = LV_HOR_RES;
    g_lcd_ctx.layer_config_osd1.x = 0;
    g_lcd_ctx.layer_config_osd1.y = 0;
    g_lcd_ctx.layer_config_osd1.width = LV_HOR_RES/2;
    g_lcd_ctx.layer_config_osd1.height = 1;
    g_lcd_ctx.layer_config_osd1.alpha = 0xff;

    g_lcd_ctx.wb_config.wb_enable = 0;
    g_lcd_ctx.layer_config_img.layer_enable = 0;

    lcdc_update_output_setting(&g_lcd_ctx.output_config, LV_HOR_RES/2, 1);
    for(i = 0; i < LV_VER_RES/8; i++) {
        g_lcd_ctx.layer_config_osd1.layer_enable = 1;
        g_lcd_ctx.layer_config_osd1.buf1 = (void *)(lcd_fb + i*LV_HOR_RES);
        st7567_panel_invalid(0, i*8, LV_HOR_RES-1, i*8+7);

        lcd_spi_before_refresh();
        lcdc_sync(&g_lcd_ctx, 1); /* polling mode */
        lcd_spi_after_refresh();
    }
    //printf("flush : %ld, %ld, %ld, %ld, [%ld]\n",x1, x2, y1, y2, lv_tick_elaps(start));
#else
    uint8_t c, p;
    //uint32_t start = lv_tick_get();
    for(p = y1 / 8; p <= y2 / 8; p++) {
        lcd_spi_write_cmd(0xB0 | pagemap[p], 8);
        lcd_spi_write_cmd(0x00 | (x1 & 0xf), 8);
        lcd_spi_write_cmd(0x10 | ((x1 >> 4) & 0xf), 8);
        lcd_spi_write_cmd(0xE0, 8);

        for(c = x1; c <= x2; c++) {
            lcd_spi_write_data(lcd_fb[LV_HOR_RES * p + c], 8);
        }
    }
    //printf("flush : %ld, %ld, %ld, %ld, [%ld]\n",x1, x2, y1, y2, lv_tick_elaps(start));
#endif
    lv_flush_ready();
}

struct crane_panel_t st7567_spi = {
    .probe = st7567_spi_probe,
    .readid = st7567_spi_read_id,
    .panel_onoff = st7567_spi_onoff,
    .flush = st7567_spi_flush,
};

#endif /*USE_LCD_PANEL_ST7567_SPI_4WIRE_1LANE_1IF*/
